/*
 * Copyright (c) 2010-2013 Ruben Chakhmakhchyan. All rights reserved.
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Eclipse Public License
 * Version 1.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://opensource.org/licenses/EPL-1.0 and read it before using this file.
 */

#include <stdlib.h>
#include <string.h>

#include "pdf_writer.h"
#include "pdf_stream.h"
#include "pdf_obj.h"

#include "list.h"
#include <stdio.h>
#include <string.h>
#include "convert.h"


PdfWriter::PdfWriter( PdfStream& stream )
    : m_stream( stream )
{
}

PdfWriter& PdfWriter::write( byte_t value )
{
    m_stream.writeByte( value );

    return *this;
}

PdfWriter& PdfWriter::writeBytes( byte_t* data, uint_t size )
{
    m_stream.write( data, size );

    return *this;
}

PdfWriter& PdfWriter::writeInt( int value )
{
    char buffer[ 16 ] = { 0 };
    //memset( buffer, 0, 16 );

    //::_itoa( value, buffer, 10 );
    Convert::ToString( buffer, value );

    write( buffer );

    return *this;
}

PdfWriter& PdfWriter::writeInt( int value, int padLeft )
{
    char* paddedBuffer = new char[ padLeft + 1 ];
    ::memset( paddedBuffer, '0', padLeft );
    paddedBuffer[ padLeft ] = 0;

    char buffer[ 16 ] = { 0 };
    //memset( buffer, 0, 16 );

    //::_itoa( value, buffer, 10 );
    Convert::ToString( buffer, 10 );

    uint_t len = String::GetLength( buffer );

    ::memcpy( paddedBuffer + ( padLeft - len ), buffer, len );

    write( paddedBuffer );

    delete paddedBuffer;

    return *this;
}

PdfWriter& PdfWriter::write( const char* value )
{
    const char* cp = value;
    char c;
    while( c = *cp )
    {
        m_stream.writeByte( c );
        cp++;
    }

    return *this;
}

PdfWriter& PdfWriter::write( byte_t* value, uint_t length )
{
    return *this;
}

PdfWriter& PdfWriter::writeLF()
{
    return write( ( byte_t ) 0x0A );
}

PdfWriter& PdfWriter::writeCR()
{
    return write( ( byte_t ) 0x0D );
}

PdfWriter& PdfWriter::writeObject( const PdfObject& obj )
{
    uint_t type = ( uint_t ) obj.getType();
    switch ( type )
    {
    case PdfObject::Type::Image:
        {
            const PdfImage& image = reinterpret_cast<const PdfImage&>( obj );

            uint_t imageSize = image.getSize();
            //uint_t bitsPerComponent = image.getBitsPerComponent();
            uint_t bitCount = image.getBitCount();

            // object
            //writeInt( obj.getId() ).write( " 0 obj" ).writeLF().write( "<< " ).writeLF().write( "   /Type /XObject" ).writeLF().write( "   /Subtype /Image" ).writeLF().write( "   /Name /").write( image.getName() ).writeLF().write( "   /BitsPerComponent 8" ).writeLF().write( "   /Width " ) .writeInt( image.getWidth() ).writeLF().write( "   /Height ").writeInt( image.getHeight() ).writeLF().write( "   /ColorSpace /DeviceRGB" ).writeLF().write( "   /Filter /DCTDecode" ).writeLF().write( "   /Length " ).writeInt( imageSize ).writeLF().write( ">>" ).writeLF().write( "stream" ).writeLF();


            writeInt( obj.getId() ).write( " 0 obj" ).writeLF()
                .write( "<< " ).writeLF()
                .write( " /Type /XObject" ).writeLF()
                .write( " /Subtype /Image" ).writeLF()
                .write( " /Name /").write( image.getName() ).writeLF()
                .write( " /Width " ).writeInt( image.getWidth() ).writeLF()
                .write( " /Height ").writeInt( image.getHeight() ).writeLF();

            switch ( image.getEncoding() )
            {
            case PdfEncoding::CCITTG4:

                //write( "   /ColorSpace /DeviceGray /BitsPerComponent 1" ).write( "/Filter /CCITTFaxDecode/DecodeParms<</K -1/Columns " ).writeInt( image.getWidth() ).write( "/Rows " ).writeInt( image.getHeight() ).write( "/BlackIs1 true>>" ).writeLF().write( "/Interpolate true" );

                write( " /ColorSpace /DeviceGray /BitsPerComponent 1 /BlackIs1 true" )
                    .write( " /Filter /CCITTFaxDecode /DecodeParms << /K -1 /Columns " ).writeInt( image.getWidth() )
                    .write( "/Rows " ).writeInt( image.getHeight() )
                    .write( ">> " );

                //write( "/ColorSpace [ /Indexed /DeviceRGB 1 <FFFFFF000000> ]" ).writeLF().write( "/DeviceGray /BitsPerComponent 1" ).write( "/Filter /CCITTFaxDecode/DecodeParms<</K -1/Columns " ).writeInt( image.getWidth() ).write( "/Rows " ).writeInt( image.getHeight() ).write( "/BlackIs1 true>>" ).writeLF().write( "/Interpolate true" );;

                break;

            case PdfEncoding::Flate:

                // bits per component
                write( bitCount == 1 ? " /BitsPerComponent 1" : " /BitsPerComponent 8" );

                // palette
                //write( image.getBlackIs1() ? "   /BlackIs1 true" : "   /BlackIs1 false" ); // not applicable in encoding other than CCITT

                // color space
                writeLF().write( " /ColorSpace " );
                switch ( bitCount )
                {
                case 1:
                case 8: write( "/DeviceGray" ); break;
                case 24: write( "/DeviceRGB" ); break;
                }

                writeLF().write( " /Filter /FlateDecode" );

                break;

            case PdfEncoding::DCT:

                write( " /BitsPerComponent 8" ).writeLF().write( " /ColorSpace " );

                switch ( bitCount )
                {
                case 8: write( "/DeviceGray" ); break;
                case 24: write( "/DeviceRGB" ); break;
                }

                writeLF().write( " /Filter /DCTDecode" );

                break;
            }

            writeLF().write( " /Length " ).writeInt( imageSize ).writeLF().write( ">>" ).writeLF().write( "stream" ).writeLF();


            byte_t* streamData = image.getData();

            // stream
            writeBytes( streamData, imageSize );

            // end
            writeLF().write( "endstream" ).writeLF().write( "endobj" ).writeLF();
        }
        break;

    case PdfObject::Type::Catalog:
        {
            write( "1 0 obj\n<< \n /Type /Catalog\n /Pages 2 0 R\n /PageLayout /OneColumn\n /PageMode /UseNone\n>>\nendobj" ).writeLF();
        }
        break;

    case PdfObject::Type::Pages:
        {
            const PdfPages& pages = reinterpret_cast<const PdfPages&>( obj );

            write( "2 0 obj\n<< \n /Type /Pages\n /Kids [" );

            const List<PdfPage*>& pageArray = pages.getPages();

            for ( auto page : pageArray )
            {
                writeInt( page->getId() ).write( " 0 R " );
            }
            write( "]" ).write("\n /Count " ).writeInt( pages.getCount() ).write( "\n>>\nendobj" ).writeLF();
        }
        break;

    case PdfObject::Type::Page:
        {
            const PdfPage& page = reinterpret_cast<const PdfPage&>( obj );
            const PdfImage& image = page.getImage();
            uint_t id = page.getId();
            uint_t imageWidth = image.getWidth(), imageHeight = image.getHeight();
            uint_t resolution = image.getResolution();
            uint_t pageWidth = ( uint_t )( ( double ) imageWidth / ( double ) resolution * 72.0F );
            uint_t pageHeight = ( uint_t )( ( double ) imageHeight / ( double ) resolution * 72.0F );

            writeInt( id++ ).write( " 0 obj\n<< \n /Type /Page\n /Parent 2 0 R\n /MediaBox [0 0 " ).writeInt( pageWidth ).write( " " ).writeInt( pageHeight ).write( "]\n /Resources\n<< \n /ProcSet [/PDF /Text ]\n /XObject\n<< \n   /" ).write( image.getName() ).write( " " ).writeInt( image.getId() ).write( " 0 R\n>>\n>>\n /Contents " ).writeInt( id ).write( " 0 R\n>>\nendobj" ).writeLF(); //595 842
        }
        break;

    case PdfObject::Type::PageContent:
        {
            const PdfPageContent& pageContent = reinterpret_cast<const PdfPageContent&>( obj );
            const PdfPage& page = pageContent.getPage();
            PdfImage& image = page.getImage();
            uint_t id = page.getId() + 1;

            double dpi = image.getResolution();
            uint_t width = ( unsigned int ) ( image.getWidth() * 72 / dpi );
            uint_t height = ( unsigned int ) ( image.getHeight() * 72 / dpi );

            char contentStream[ 128 ] = { 0 };
            ::sprintf( contentStream, "q\n%i 0 0 %i 0 0 cm\n/%s Do\nQ", width, height, image.getName() );
            size_t contentStreamLength = String::GetLength( contentStream );

            writeInt( id ).write( " 0 obj\n<< \n /Length " ).writeInt( contentStreamLength ).write( "\n>>\nstream\n" )
                .write( contentStream )
                .write( "\nendstream\nendobj" ).writeLF();
        }
        break;
    }

    return *this;
}

uint_t PdfWriter::getPosition()
{
    return m_stream.getPosition();
}
